<?php
/**
 * Created by PhpStorm.
 * User: zxwwjs@hotmail.com
 * Date: 2017-7-6
 * Time: 16:21
 */

namespace App\Repositories\BaseInterface;

use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Container\Container as App;

abstract class Repository implements RepositoryInterface {

  /**
   * @var Model
   */
  protected $model;

  /**
   * @var Model
   */
  protected $newModel;
  /**
   * Prevents from overwriting same criteria in chain usage
   * @var bool
   */
  protected $preventCriteriaOverwriting = true;
  /**
   * @var App
   */
  private $app;

  /**
   * @param App $app
   * @param Collection $collection
   *
   * @throws \Bosnadev\Repositories\Exceptions\RepositoryException
   */
  public function __construct( App $app, Collection $collection ) {
    $this->app      = $app;
    $this->criteria = $collection;
    $this->makeModel();
  }


  /**
   * @return \Illuminate\Database\Eloquent\Builder
   * @throws RepositoryException
   */
  public function makeModel() {
    return $this->setModel( $this->model() );
  }

  /**
   * Set Eloquent Model to instantiate
   *
   * @param $eloquentModel
   *
   * @return Model
   * @throws RepositoryException
   */
  public function setModel( $eloquentModel ) {
    $this->newModel = $this->app->make( $eloquentModel );

    if ( ! $this->newModel instanceof Model ) {
      throw new RepositoryException( "Class {$this->newModel} must be an instance of Illuminate\\Database\\Eloquent\\Model" );
    }

    return $this->model = $this->newModel;
  }

  /**
   * Specify Model class name
   *
   * @return mixed
   */
  public abstract function model();

  /**
   * @param array $columns
   *
   * @return mixed
   */
  public function all( $columns = array( '*' ) ) {
    return $this->model->get( $columns );
  }

  /**
   * @param array $relations
   *
   * @return $this
   */
  public function with( array $relations ) {
    $this->model = $this->model->with( $relations );

    return $this;
  }

  /**
   * @param  string $value
   * @param  string $key
   *
   * @return array
   */
  public function lists( $value, $key = null ) {

    $lists = $this->model->lists( $value, $key );
    if ( is_array( $lists ) ) {
      return $lists;
    }

    return $lists->all();
  }

  /**
   * @param int $perPage
   * @param array $columns
   *
   * @return mixed
   */
  public function paginate( $perPage = 25, $columns = array( '*' ) ) {

    return $this->model->paginate( $perPage, $columns );
  }

  /**
   * @param array $data
   *
   * @return mixed
   */
  public function create( array $data ) {
    return $this->model->create( $data );
  }

  /**
   * save a model without massive assignment
   *
   * @param array $data
   *
   * @return bool
   */
  public function saveModel( array $data ) {
    foreach ( $data as $k => $v ) {
      $this->model->$k = $v;
    }

    return $this->model->save();
  }

  /**
   * @param array $data
   * @param $id
   * @param string $attribute
   *
   * @return mixed
   */
  public function update( array $data, $id, $attribute = "id" ) {
    return $this->model->where( $attribute, '=', $id )->update( $data );
  }

  /**
   * @param  array $data
   * @param  $id
   *
   * @return mixed
   */
  public function updateRich( array $data, $id ) {

    if ( ! ( $model = $this->model->find( $id ) ) ) {
      return nulll;
    }
    if( $model->fill( $data )->save()){
      return $model;
    }else{
      return nulll;
    }
  }

  /**
   * @param $id
   *
   * @return mixed
   */
  public function delete( $id ) {
    return $this->model->destroy( $id );
  }

  /**
   * @param $id
   * @param array $columns
   *
   * @return mixed
   */
  public function find( $id, $columns = array( '*' ) ) {

    return $this->model->find( $id, $columns );
  }

  /**
   * @param $attribute
   * @param $value
   * @param array $columns
   *
   * @return mixed
   */
  public function findBy( $attribute, $value, $columns = array( '*' ) ) {

    return $this->model->where( $attribute, '=', $value )->first( $columns );
  }

  /**
   * @param $attribute
   * @param $value
   * @param array $columns
   *
   * @return mixed
   */
  public function findAllBy( $attribute, $value, $columns = array( '*' ) ) {

    return $this->model->where( $attribute, '=', $value )->get( $columns );
  }

  /**
   * Find a collection of models by the given query conditions.
   *
   * @param array $where
   * @param array $columns
   * @param bool $or
   *
   * @return \Illuminate\Database\Eloquent\Collection|null
   */
  public function findWhere( $where, $columns = [ '*' ], $or = false ) {

    $model = $this->model;

    foreach ( $where as $field => $value ) {
      if ( $value instanceof \Closure ) {
        $model = ( ! $or )
          ? $model->where( $value )
          : $model->orWhere( $value );
      } elseif ( is_array( $value ) ) {
        if ( count( $value ) === 3 ) {
          list( $field, $operator, $search ) = $value;
          $model = ( ! $or )
            ? $model->where( $field, $operator, $search )
            : $model->orWhere( $field, $operator, $search );
        } elseif ( count( $value ) === 2 ) {
          list( $field, $search ) = $value;
          $model = ( ! $or )
            ? $model->where( $field, '=', $search )
            : $model->orWhere( $field, '=', $search );
        }
      } else {
        $model = ( ! $or )
          ? $model->where( $field, '=', $value )
          : $model->orWhere( $field, '=', $value );
      }
    }

    return $model->get( $columns );
  }

}
